using System;
using System.Collections.Generic;
using System.Text;

namespace ShearwaterLogReader
{
    /// <summary>
    /// Represents a breathing gas. Maintains fractions of O2, He and N2, 
    /// provides MOD and accumulates volume used in a dive.
    /// </summary>
    public class Gas : IComparable
    {
        #region Members

        private double fHe, fO2;    // Gas fractions
        private double mod;         // Maximum operating depth 
        private double volume;      // Accumulate gas volume
        private bool enable;        // Gas is enabled for use

        #endregion

        #region Properties

        virtual public double FHe
        {
            get
            {
                return fHe;
            }

            set
            {
                fHe = value;
            }
        }

        public int PercentO2
        {
            get
            {
                return (int)(this.FO2 * 100D);
            }

            set
            {
                this.fO2 = (double)value / 100;
                this.MOD = Util.CalcMod(this.FO2);
            }
        }

        public int PercentHe
        {
            get
            {
                return (int)(this.FHe * 100D);
            }

            set
            {
                this.FHe = (double)value / 100;
            }
        }

        public int PercentN2
        {
            get
            {
                return (int)(this.FN2 * 100D);
            }
        }

        /// <summary>
        /// Equivalent narcotic depth at MOD. 
        /// </summary>
        public double END
        {
            get
            {
                return Util.CalcEND(this);
            }
        }

        public double Ceiling
        {
            get
            {
                return Util.CalcCeiling(this.FO2);
            }
        }

        virtual public double FO2
        {
            get
            {
                return fO2;
            }

            set
            {
                fO2 = value;
            }

        }

        virtual public double FN2
        {
            get
            {
                double n = 1.0 - fHe - fO2;

                if (n < 0.0001)
                    return 0;
                else
                    return n;
            }
        }

        virtual public double MOD
        {
            get
            {
                return mod;
            }

            set
            {
                mod = value;
            }
        }

        virtual public double Volume
        {
            get
            {
                return volume;
            }

            set
            {
                volume = value;
            }
        }

        virtual public bool Enable
        {
            get
            {
                return enable;
            }

            set
            {
                enable = value;
            }
        }
        
        virtual public string ShortName
        {
            get
            {
                return this.ToString();
            }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// Constructor for Gas objects. Fractions must add to less than equal to 1.0. 
        /// Remainder assumed Nitrogen. If constructed with erroneous data is set up as air.
        /// </summary>
        /// <param name="fHe">Fraction of helium (0 - 1.0)</param>
        /// <param name="fO2">Fraction of Oxygen (0 - 1.0)</param>
        /// <param name="mod">Maximum Operating depth in m (ft)</param>
        public Gas(double fO2, double fHe, double mod)
        {
            SetGas(fHe, fO2, mod);
            enable = true; //Boolean.TRUE;
            this.volume = 0;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="fHe"></param>
        /// <param name="fO2"></param>
        public Gas(double fO2, double fHe)
        {
            double mod = ((Prefs.Current.PPO2Max / fO2) - 1) * 10;

            SetGas(fHe, fO2, mod);
            enable = true; //Boolean.TRUE;
            this.volume = 0;
        }

        /// <summary>
        /// Empty constructor for bean interface.
        /// </summary>
        public Gas()
        {
            enable = true;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Method to validate a field for limits only 
        /// </summary>
        /// <param name="field"></param>
        /// <param name="value_Renamed"></param>
        /// <returns></returns>
        public static bool Validate(string field, double value_Renamed)
        {
            if (field.Equals("fHe") || field.Equals("fO2"))
                return (value_Renamed >= 0 && value_Renamed <= 1.0);

            if (field.Equals("mod"))
                // Need to hard code nominal max value due to potential of prefs not being fully set up when this is called
                return (value_Renamed >= 0 && value_Renamed <= 900);

            return false;
        }

        /// <summary>
        /// Method to validate all inputs (fO2, fHe and MOD)
        /// </summary>
        /// <param name="fHe"></param>
        /// <param name="fO2"></param>
        /// <param name="mod"></param>
        /// <returns></returns>
        public static bool Validate(double fHe, double fO2, double mod)
        {
            bool passed = true;

            // Check individual fields for bounds   
            passed = (passed && Validate("fHe", fHe));
            passed = (passed && Validate("fO2", fO2));
            passed = (passed && Validate("mod", mod));

            if (!passed)
                return false;

            // Check combined fractions
            passed = (passed && (fHe + fO2) <= 1.0);

            if (!passed)
                return false;

            // Check MOD for sensible value   
            if (fO2 == 0 && mod == 0)
                // Leave empty gases alone to allow construction
                return passed;

            //if (Prefs.Current != null)
            //    // Need to check that prefs exists. We can get to this point during the initilisation of the prefs object
            //    passed = ((mod + Prefs.Current.PConversion) / Prefs.Current.PConversion * fO2) <= Prefs.Current.MaxMOD;
            return passed;
        }

        /// <summary> 
        /// Override the clone method to make it public
        /// </summary>
        /// <returns>Cloned Gas object</returns>
        /// <throws>CloneNotSupportedException Never thrown and required for Cloneable interface</throws>
        public virtual object Clone()
        {
            return base.MemberwiseClone();
        }

        /// <summary>
        /// Used to implement the Comparable interface. To compare gases
        /// based on their mod (Maximum Operating Depth).
        /// </summary>
        /// <param name="Object">(Gas) to compare to</param>
        /// <returns>Integer, Mod of compared Gas - Mod of this gas</returns>
        public virtual int CompareTo(object o)
        {
            double m;
            Gas g;

            g = (Gas)o;
            m = g.MOD;
            
            return (int)(m - mod);
        }

        /// <summary>
        /// Sets the gas fractions or creates Air by default.
        /// </summary>
        /// <param name="fHe"></param>
        /// <param name="fO2"></param>
        /// <param name="mod"></param>
        public virtual void SetGas(double fHe, double fO2, double mod)
        {
            if (Validate(fHe, fO2, mod))
            {
                this.fHe = fHe;
                this.fO2 = fO2;
                this.mod = mod;
            }
            else
            {
                throw new GasException("Validate(fHe, fO2, mod) returned false");
            }
        }

        /// <summary>
        /// Construct a human readable name for this gas and override ToString method.
        /// </summary>
        /// <param name="showVolume"></param>
        /// <returns></returns>
        public string ToString(bool showVolume)
        {
            string name;
            string composition;

            if (fHe == 0)
            {
                composition = System.Convert.ToString((int)(fO2 * 100));
            }
            else
            {
                composition = ((int)(fO2 * 100) + "/" + (int)(fHe * 100));
            }

            if (fHe > 0)
            {
                if ((fHe + fO2) == 1.0)
                    name = "Heliox " + composition;
                else
                    name = "Trimix " + composition;
            }
            else if (fO2 == 0.21)
                name = "Air";
            else if (fO2 == 1.0)
                name = "Oxygen";
            else
                name = "Nitrox " + composition;

            if (showVolume)
            {
                name += " " + (int)this.Volume;

                if (Prefs.Current.Units == Units.Metric)
                {
                    name += " litres";
                }
                else
                {
                    name += " cf";
                }
            }

            return (name);
        }

        /// <summary>
        /// Overrides default ToString method. 
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return this.ToString(false);
        }

        #endregion
    }
}
